package org.swim.core.manager;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ejb.Stateless;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;
import net.sf.json.JsonConfig;
import net.sf.json.util.EnumMorpher;
import net.sf.json.util.JSONUtils;

import org.swim.core.entities.Ability;
import org.swim.core.entities.Administrator;
import org.swim.core.entities.Notification;
import org.swim.core.entities.User;
import org.swim.local.manager.UserManagerLocal;
import org.swim.remote.entities.IAbility;
import org.swim.remote.entities.IFriendship;
import org.swim.remote.entities.INotification;
import org.swim.remote.entities.IUser;
import org.swim.remote.entities.IUser.Status;
import org.swim.remote.exceptions.AbilityException;
import org.swim.remote.exceptions.PasswordException;
import org.swim.remote.exceptions.SwimException;
import org.swim.remote.exceptions.SwimException.Severity;
import org.swim.remote.exceptions.TokenException;
import org.swim.remote.exceptions.UserException;
import org.swim.remote.manager.UserManagerRemote;
import org.swim.utils.SwimLog;

/**
 * Session Bean implementation class UserManager
 */
@Stateless
public class UserManager extends SwimManager implements UserManagerRemote,
		UserManagerLocal {

	private static JsonConfig USER_JSON_CFG;
	private UserLogged ul = UserLogged.getUserLogged();

	static {
		JSONUtils.getMorpherRegistry().registerMorpher(
				new EnumMorpher(IUser.Status.class));

		USER_JSON_CFG = new JsonConfig();
		USER_JSON_CFG.setRootClass(User.class);
	}

	@PersistenceContext(unitName = "swimds")
	private EntityManager em;

	@Deprecated
	public IUser post(JSONObject json) {
		User user = (User) JSONSerializer.toJava(json, USER_JSON_CFG);
		em.persist(user);
		return user;
	}

	@Deprecated
	public IUser delete(Integer id) {
		User user = em.find(User.class, id);
		em.remove(user);
		return user;
	}

	@Deprecated
	public IUser put(JSONObject json) {
		User user = (User) JSONSerializer.toJava(json, USER_JSON_CFG);
		em.merge(user);

		return user;
	}

	public IUser get(Integer id) throws SwimException {
		Query q = em.createQuery("SELECT u FROM User u WHERE u.id = '" + id
				+ "'");

		if (q.getResultList().size() != 1)
			throw new SwimException("USER DOESN'T EXIST", Severity.ERROR);

		return (User) q.getSingleResult();
	}

	@SuppressWarnings("unchecked")
	public List<IUser> getAll() {
		Query q = em.createQuery("SELECT u FROM User u");
		return q.getResultList();
	}

	public String signUp(JSONObject object) throws UserException {

		checkPassword(object.getString(IUser.Attribute.PASSWORD.toString()));

		checkEmail(object.getString(IUser.Attribute.EMAIL.toString()));

		checkUserFullName(object.getString(IUser.Attribute.NAME.toString()),
				object.getString(IUser.Attribute.SURNAME.toString()));

		Query q = em.createQuery("SELECT u FROM User u WHERE u.email = '"
				+ object.getString(IUser.Attribute.EMAIL.toString())
				+ "' and u.status = '" + Status.UNREGISTERED + "'");

		User user;
		if (q.getResultList().size() != 0) {
			user = (User) q.getResultList().get(0);
			user.setName(object.getString(IUser.Attribute.NAME.toString())
					.toUpperCase());
			user.setSurname(object
					.getString(IUser.Attribute.SURNAME.toString())
					.toUpperCase());
			user.setPassword(object.getString(IUser.Attribute.PASSWORD
					.toString()));
		} else {
			user = (User) JSONSerializer.toJava(object, USER_JSON_CFG);
			if (!user.getName().equals(IUser.Default.NAME.toString()))
				user.setName(user.getName().toUpperCase());
			if (!user.getSurname().equals(IUser.Default.SURNAME.toString()))
				user.setSurname(user.getSurname().toUpperCase());
			SwimLog.getSwimLog().debug(UserManager.class,
					"[SignUp] Non" + user.getFullName());
		}

		user.setStatus(Status.REGISTERED);

		if (q.getResultList().size() != 0) {
			em.merge(user);
		} else {
			em.persist(user);
		}

		return ul.put(user.getEmail(), user.getId());
	}

	public String signIn(JSONObject object) throws UserException {
		checkPassword(object.getString(IUser.Attribute.PASSWORD.toString()));

		checkEmail(object.getString(IUser.Attribute.EMAIL.toString()));

		User user = (User) JSONSerializer.toJava(object, USER_JSON_CFG);

		Query q = em.createQuery("SELECT u FROM User u WHERE u.email = '"
				+ user.getEmail() + "' and u.password = '" + user.getPassword()
				+ "'");

		if (q.getResultList().isEmpty() || q.getResultList().size() != 1) {
			throw new UserException("USER NOT VALID");
		}

		User userRes = (User) q.getSingleResult();

		if (userRes.getStatus() != Status.REGISTERED)
			throw new UserException("UTENTE NOT REGISTERED");

		return ul.put(userRes.getEmail(), userRes.getId());
	}

	public void signOut(JSONObject object) throws TokenException {
		String token = object.getString("token");
		ul.remove(token);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.swim.core.manager.UserManagerLocal#addAbility(net.sf.json.JSONObject)
	 */
	public IUser addAbility(JSONObject object) throws NamingException,
			SwimException {
		String tokenUser = object.getString("token");

		Integer id = UserLogged.getUserLogged().checkUserLogged(tokenUser);

		Ability ability = getAml().checkAbility(object);

		User user = (User) get(id);

		user.setAnAbility(ability);

		return user;
	}

	public IUser removeAbility(JSONObject object) throws NamingException,
			SwimException {
		String tokenUser = object.getString("token");

		Integer id = UserLogged.getUserLogged().checkUserLogged(tokenUser);

		Ability ability = getAml().checkAbility(object);

		User user = (User) get(id);

		if (!user.getAbilitiesSet().contains(ability))
			throw new UserException("THE USER DOESN'T HAVE THIS ABILITY");

		user.removeAnAbility(ability);

		return user;
	}

	public void removeAccount(JSONObject object) throws SwimException {

		String tokenUser = object.getString("token");

		Integer id = UserLogged.getUserLogged().checkUserLogged(tokenUser);

		checkPassword(object.getString(IUser.Attribute.PASSWORD.toString()));

		User user = (User) get(id);

		if (user.getStatus() != Status.REGISTERED)
			throw new UserException("USER NOT REGISTERED", Severity.FATAL_ERROR);

		String pass = object.getString(IUser.Attribute.PASSWORD.toString());

		if (!user.getPassword().equals(pass))
			throw new PasswordException("PASSWORD NOT VALID");

		ul.remove(tokenUser);
		em.remove(user);
	}

	public IUser updateUser(JSONObject object) throws SwimException {
		String tokenUser = object.getString("token");

		Integer id = UserLogged.getUserLogged().checkUserLogged(tokenUser);

		checkEmail(object.getJSONObject("user").getString(
				IUser.Attribute.EMAIL.toString()));

		checkUserFullName(
				object.getJSONObject("user").getString(
						IUser.Attribute.NAME.toString()),
				object.getJSONObject("user").getString(
						IUser.Attribute.SURNAME.toString()));

		User user = (User) get(id);

		User userJson = (User) JSONSerializer.toJava(
				object.getJSONObject("user"), USER_JSON_CFG);
		
		if(!user.getEmail().equals(userJson.getEmail()))
			throw new UserException("YOU CAN'T CHANGE THE EMAIL");

		user.setName(userJson.getName().toUpperCase());

		user.setSurname(userJson.getSurname().toUpperCase());

		if (!userJson.getPassword().equals(IUser.Default.PASSWORD.toString()))
			user.setPassword(userJson.getPassword());

		em.merge(user);

		return user;
	}

	@SuppressWarnings("unchecked")
	public List<IUser> findNoFriendsByAbility(JSONObject object)
			throws TokenException, AbilityException, NamingException {

		String tokenUser = object.getString("token");

		Integer id = UserLogged.getUserLogged().checkUserLogged(tokenUser);

		JSONArray abilityList = object.getJSONArray("abilities");

		Integer idAb;

		String s = "SELECT u FROM User u WHERE NOT EXISTS (SELECT f FROM "
				+ "Friendship f WHERE ((f.userBySenderId = u.id and f.userByReceiverId = '"
				+ id + "') " + "or  (f.userBySenderId = '" + id
				+ "' and f.userByReceiverId = u.id) ) and f.status = '"
				+ IFriendship.Status.CONFIRMED + "' ) " + "and u.id != '" + id
				+ "' and u.status = '" + IUser.Status.REGISTERED.name() + "' "
				+ "and EXISTS";

		for (int i = 0; i < abilityList.size() - 1; i++) {
			idAb = getAml().getIdAbility(abilityList.getString(i));
			s += "(SELECT a FROM Ability a WHERE a.id = '" + idAb
					+ "' and a.status = '"
					+ IAbility.Status.CONFIRMED.toString() + "' and '" + idAb
					+ "' in elements (u.abilities) )and EXISTS";
		}

		idAb = getAml().getIdAbility(
				abilityList.getString(abilityList.size() - 1));
		s += "(SELECT a FROM Ability a WHERE a.id = '" + idAb
				+ "' and a.status = '" + IAbility.Status.CONFIRMED.toString()
				+ "' and '" + idAb + "' in elements (u.abilities) )";

		SwimLog.getSwimLog().debug(UserManager.class,
				"[findNoFriendsByAbility] " + s);

		Query q = em.createQuery(s);

		return q.getResultList();
	}

	@SuppressWarnings("unchecked")
	public List<IUser> findNoFriendsByName(JSONObject object)
			throws TokenException {

		String tokenUser = object.getString("token");

		Integer id = UserLogged.getUserLogged().checkUserLogged(tokenUser);

		String iniziale = object.getString("query");

		String s = "SELECT u FROM User u WHERE NOT EXISTS (SELECT f FROM "
				+ "Friendship f WHERE ((f.userBySenderId = u.id and f.userByReceiverId = '"
				+ id + "') " + "or  (f.userBySenderId = '" + id
				+ "' and f.userByReceiverId = u.id) ) and f.status = '"
				+ IFriendship.Status.CONFIRMED + "' ) and u.id != '" + id
				+ "' and u.status = '" + IUser.Status.REGISTERED.name()
				+ "' and (u.name LIKE (?1)" + " or u.surname LIKE (?1)"
				+ " or concat(u.name, ' ', u.surname) LIKE (?1)"
				+ " or concat(u.surname, ' ', u.name) LIKE (?1)" + ")";

		SwimLog.getSwimLog().debug(UserManager.class,
				"[findNoFriendsByName] " + s);

		Query q = em.createQuery(s);

		return q.setParameter(1, (iniziale.toUpperCase() + "%"))
				.getResultList();
	}

	@SuppressWarnings("unchecked")
	public List<IUser> findFriendsByAbility(JSONObject object)
			throws TokenException, AbilityException, NamingException {

		String tokenUser = object.getString("token");

		Integer id = UserLogged.getUserLogged().checkUserLogged(tokenUser);

		JSONArray abilityList = object.getJSONArray("abilities");

		Integer idAb;

		String s = "SELECT u FROM User u WHERE EXISTS (SELECT f FROM "
				+ "Friendship f WHERE ((f.userBySenderId = u.id and f.userByReceiverId = '"
				+ id + "') " + "or  (f.userBySenderId = '" + id
				+ "' and f.userByReceiverId = u.id)) and f.status = '"
				+ IFriendship.Status.CONFIRMED + "') " + "and u.id != '" + id
				+ "' and u.status = '" + IUser.Status.REGISTERED.name() + "' "
				+ "and EXISTS";

		for (int i = 0; i < abilityList.size() - 1; i++) {
			idAb = getAml().getIdAbility(abilityList.getString(i));
			s += "(SELECT a FROM Ability a WHERE a.id = '" + idAb
					+ "' and a.status = '"
					+ IAbility.Status.CONFIRMED.toString() + "' and '" + idAb
					+ "' in elements (u.abilities) )and EXISTS";
		}

		idAb = getAml().getIdAbility(
				abilityList.getString(abilityList.size() - 1));
		s += "(SELECT a FROM Ability a WHERE a.id = '" + idAb
				+ "' and a.status = '" + IAbility.Status.CONFIRMED.toString()
				+ "' and '" + idAb + "' in elements (u.abilities) )";

		SwimLog.getSwimLog().debug(UserManager.class,
				"[findNoFriendsByAbility] " + s);

		Query q = em.createQuery(s);

		return q.getResultList();
	}

	@SuppressWarnings("unchecked")
	public List<IUser> findFriendsByName(JSONObject object)
			throws TokenException {

		String tokenUser = object.getString("token");

		Integer id = UserLogged.getUserLogged().checkUserLogged(tokenUser);

		String iniziale = object.getString("query");

		String s = "SELECT u FROM User u WHERE EXISTS (SELECT f FROM "
				+ "Friendship f WHERE ((f.userBySenderId = u.id and f.userByReceiverId = '"
				+ id + "') " + "or  (f.userBySenderId = '" + id
				+ "' and f.userByReceiverId = u.id)) and f.status = '"
				+ IFriendship.Status.CONFIRMED + "' ) and u.id != '" + id
				+ "' and u.status = '" + IUser.Status.REGISTERED.name()
				+ "' and (u.name LIKE (?1)" + " or u.surname LIKE (?1)"
				+ " or concat(u.name, ' ', u.surname) LIKE (?1)"
				+ " or concat(u.surname, ' ', u.name) LIKE (?1)" + ")";

		SwimLog.getSwimLog().debug(UserManager.class,
				"[findFriendsByName] " + s);

		Query q = em.createQuery(s);

		return q.setParameter(1, (iniziale.toUpperCase() + "%"))
				.getResultList();
	}

//	@SuppressWarnings("unchecked")
//	public List<IUser> findFriends(JSONObject object) throws TokenException {
//
//		String tokenUser = object.getString("token");
//
//		Integer id = UserLogged.getUserLogged().checkUserLogged(tokenUser);
//
//		Query q = em
//				.createQuery("SELECT u FROM User u WHERE EXISTS (SELECT f FROM "
//						+ "Friendship f WHERE ((f.userBySenderId = u.id and f.userByReceiverId = '"
//						+ id
//						+ "') "
//						+ "or  (f.userBySenderId = '"
//						+ id
//						+ "' and f.userByReceiverId = u.id) ) and f.status = '"
//						+ IFriendship.Status.CONFIRMED
//						+ "' ) and u.id != '"
//						+ id
//						+ "' and u.status = '"
//						+ IUser.Status.REGISTERED.name() + "'");
//
//		return q.getResultList();
//
//	}

	@SuppressWarnings("unchecked")
	public List<INotification> getLastNotification(JSONObject object) throws TokenException, UserException {
		String tokenUser = object.getString("token");

		Integer id = UserLogged.getUserLogged().checkUserLogged(tokenUser);
		
		Query q = em.createQuery("SELECT n FROM Notification n WHERE n.receiverdId = '" + id + "' ORDER BY n.timestamp DESC");
		
		List<Notification> notifications = q.getResultList();
		List<INotification> returnNotifications = new ArrayList<INotification>();
		
		int i = 0;
		
		for (Notification notification : notifications) {
			
			SwimLog.getSwimLog().debug(UserManager.class, "Notification; " + i + " - ID: " + notification.getId().getId());
			i++;
			
			String sNot = "SELECT k FROM " + notification.getId().getType() + " k WHERE k.id = '" + notification.getId().getId() + "'";
			
			SwimLog.getSwimLog().debug(UserManager.class, sNot);
			
			Query qNot = em.createQuery(sNot);
			
			SwimLog.getSwimLog().debug(UserManager.class, "Size: " +  qNot.getResultList().size() );
			
			if(qNot.getResultList().size() != 1)
			{
				throw new UserException("NOTIFICATION ERROR", Severity.ERROR);
			}
			
			returnNotifications.add((INotification) qNot.getResultList().get(0));
		}
		
		return returnNotifications;
	}

	// Controllo che l'email sia valida e non nulla
	public void checkEmail(String email) throws UserException {
		if (email == null) {
			throw new UserException("EMAIL NULL");
		}

		// Settiamo il pattern per il confronto
		Pattern p = Pattern.compile(".+@.+\\.[a-z]+");

		// Eseguiamo il match della stringa data con il pattern
		Matcher m = p.matcher(email);

		// Salviamo il risultato del match
		boolean matchFound = m.matches();

		if (!matchFound) {
			throw new UserException("EMAIL NOT VALID");
		}
	}

	private void checkPassword(String pass) throws UserException {
		if (pass == null || pass.equals("")) {
			throw new UserException("PASSWORD NOT VALID");
		}

	}

	private void checkUserFullName(String name, String surname)
			throws UserException {
		if (name == null || name.equals(""))
			throw new UserException("NAME NOT VALID");

		if (surname == null || surname.equals(""))
			throw new UserException("SURNAME NOT VALID");
	}

	public IUser getTokenOwner(String token) throws SwimException {
		Integer id = UserLogged.getUserLogged().checkUserLogged(token);

		return get(id);
	}

	@SuppressWarnings("unchecked")
	public List<IUser> findUsers(JSONObject query) {
		String iniziale = query.getString("search-query");

		String s = "SELECT u FROM User u WHERE u.status = '"
				+ IUser.Status.REGISTERED.name() + "' and (u.name LIKE '"
				+ iniziale.toUpperCase().replace("'", "\'")
				+ "%' or u.surname LIKE '"
				+ iniziale.toUpperCase().replace("'", "\'")
				+ "%' or concat(u.name, ' ', u.surname) LIKE '"
				+ iniziale.toUpperCase().replace("'", "\'")
				+ "%' or concat(u.surname, ' ', u.name) LIKE '"
				+ iniziale.toUpperCase().replace("'", "\'") + "%')";

		Query q = em.createQuery(s);

		SwimLog.getSwimLog().debug(UserManager.class, "[findUsers] " + s);

		return q.getResultList();
	}

	@SuppressWarnings("unchecked")
	public List<IUser> findUsersByAbilities(JSONObject query)
			throws AbilityException, NamingException {

		JSONArray abilityList = query.getJSONArray("abilities");
		Integer idAb;

		String s = "SELECT u FROM User u WHERE u.status = '"
				+ IUser.Status.REGISTERED.name() + "' " + "and EXISTS";

		for (int i = 0; i < abilityList.size() - 1; i++) {
			idAb = getAml().getIdAbility(abilityList.getString(i));
			s += "(SELECT a FROM Ability a WHERE a.id = '" + idAb
					+ "' and a.status = '"
					+ IAbility.Status.CONFIRMED.toString() + "' and '" + idAb
					+ "' in elements (u.abilities) )and EXISTS";
		}

		idAb = getAml().getIdAbility(
				abilityList.getString(abilityList.size() - 1));
		s += "(SELECT a FROM Ability a WHERE a.id = '" + idAb
				+ "' and a.status = '" + IAbility.Status.CONFIRMED.toString()
				+ "' and '" + idAb + "' in elements (u.abilities) )";

		SwimLog.getSwimLog().debug(UserManager.class,
				"[findNoFriendsByAbility] " + s);

		Query q = em.createQuery(s);

		return q.getResultList();
	}

	public User getUnregisteredUser(String email) throws UserException {
		checkEmail(email);

		String s = "SELECT u FROM User u WHERE u.email = '" + email + "'";

		SwimLog.getSwimLog().debug(UserManager.class,
				"[getUnregisteredUser] " + s);

		Query q = em.createQuery(s);

		User user;

		if (q.getResultList().size() == 0) {
			user = new User();
			user.setEmail(email);
			em.persist(user);
		} else {
			user = (User) q.getSingleResult();
			if (user.getStatus() == Status.REGISTERED) {
				throw new UserException("YOU HAVE TO SIGNIN");
			}
		}

		return user;
	}

	public String administratorSignIn(JSONObject object) throws UserException {

		String password = object.getString(IUser.Attribute.PASSWORD.toString());
		String username = object.getString(IUser.Attribute.NAME.toString());

		checkPassword(password);

		if (username == null || username.equals(""))
			throw new UserException("ADMINISTRATOR USERNAME NOT VALID");

		Query q = em
				.createQuery("SELECT ad FROM Administrator ad WHERE ad.user = '"
						+ username + "' and ad.password = '" + password + "'");

		if (q.getResultList().isEmpty() || q.getResultList().size() != 1) {
			throw new UserException("ADMINISTRATOR NOT VALID");
		}

		Administrator admin = (Administrator) q.getSingleResult();

		return ul.administratorPut(admin.getUser(), admin.getId());
	}

	public void administratorSignOut(JSONObject object) throws TokenException {
		String token = object.getString("token");
		ul.administratorRemove(token);
	}
}
